package com.quick.develop.flowable.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.quick.develop.flowable.enums.BpmErrorCodeConstants;
import com.quick.develop.framework.utils.StringUtils;
import com.quick.develop.framework.utils.collection.CollectionUtils;
import com.quick.develop.flowable.domain.instance.BpmProcessInstanceRespVO;
import com.quick.develop.flowable.framework.convert.instance.BpmProcessInstanceConvert;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.history.HistoricProcessInstance;

import java.util.*;

import static com.quick.develop.framework.common.utils.ServiceExceptionUtils.exception;

public class FlowableUtil {

    public static void setAuthenticatedUserId(String userId) {
        Authentication.setAuthenticatedUserId(userId);
    }

    public static void clearAuthenticatedUserId() {
        Authentication.setAuthenticatedUserId(null);
    }

    /**
     * 获得 BPMN 流程中，指定的元素们
     *
     * @param model
     * @param clazz 指定元素。例如说，{@link UserTask}、{@link Gateway} 等等
     * @return 元素们
     */
    public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
        List<T> result = new ArrayList<>();
        model.getProcesses().forEach(process -> {
            process.getFlowElements().forEach(flowElement -> {
                if (flowElement.getClass().isAssignableFrom(clazz)) {
                    result.add((T) flowElement);
                }
            });
        });
        return result;
    }

    /**
     * 比较 两个bpmnModel 是否相同
     * @param oldModel  老的bpmn model
     * @param newModel 新的bpmn model
     */
    public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
        // 由于 BpmnModel 未提供 equals 方法，所以只能转成字节数组，进行比较
        return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
    }

    /**
     * 把 bpmnModel 转换成 byte[]
     * @param model  bpmnModel
     */
    public  static byte[] getBpmnBytes(BpmnModel model) {
        if (model == null) {
            return new byte[0];
        }
        BpmnXMLConverter converter = new BpmnXMLConverter();
        return converter.convertToXML(model);
    }

    /**
     * 根据节点，获取入口连线
     * @param source
     * @return
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getIncomingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getIncomingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getIncomingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getIncomingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getIncomingFlows();
        }
        return sequenceFlows;
    }
    
    /** 
     * @description   从后向前寻路，获取到达节点的所有路线
     * 不存在直接回退到子流程，但是存在回退到父级流程的情况
     * @param source 起始节点
     * @param passRoads 已经经过的点集合
     * @param hasSequenceFlow
     * @param roads 路线
     * @return java.util.List<java.util.List<org.flowable.bpmn.model.UserTask>>
     * @author junyuan.liu
     * @date 2022/5/2 11:39
     */ 
    public static List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        roads = roads == null ? new ArrayList<>() : roads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;

        // 如果该节点为开始节点，且存在上级子节点，则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
        }

        // 根据类型，获取入口连线
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);

        if (sequenceFlows != null && sequenceFlows.size() != 0) {
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // 如果发现连线重复，说明循环了，跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                // 添加经过路线
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
                }
                // 继续迭代
                roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
            }
        } else {
            // 添加路线
            roads.add(passRoads);
        }
        return roads;
    }
    /** 
     * @description  获得BPMN模型中节点的配置信息
     * @param bpmnModel 模型信息
     * @param activityId 节点ID
     * @return org.flowable.bpmn.model.Activity
     * @author junyuan.liu
     * @date 2022/5/29 10:23
     */ 
    public static Activity getElementInfo(BpmnModel bpmnModel, String activityId){
        return (Activity) bpmnModel.getFlowElement(activityId);
    }
    
    /** 
     * @description  获取流程图开始节点后的第一个节点 有且只有一个用户任务节点  约定而成
     * @param model 模型
     * @return org.flowable.bpmn.model.UserTask
     * @author junyuan.liu
     * @date 2022/6/6 11:53
     */ 
    public static UserTask getFirstUserTask(BpmnModel model){
        UserTask task = null;
        Optional.ofNullable(model.getProcesses())
                .orElseThrow(() -> exception(BpmErrorCodeConstants.MODEL_IS_ERROR));
        Process process = model.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        for (FlowElement flowElement : flowElements){
            if (flowElement.getClass().isAssignableFrom(StartEvent.class)) {
                List<SequenceFlow> outgoingFlows = ((StartEvent) flowElement).getOutgoingFlows();
                if (CollectionUtil.isEmpty(outgoingFlows) || outgoingFlows.size() > 1){
                    throw exception(BpmErrorCodeConstants.MODEL_FIRST_NODE_NOT_USER_TASK);
                }else {
                    String targetRef = ((StartEvent) flowElement).getOutgoingFlows().get(0).getTargetRef();
                    if (process.getFlowElementMap().get(targetRef) instanceof UserTask) {
                        task = (UserTask) process.getFlowElementMap().get(targetRef);
                    }else {
                        throw exception(BpmErrorCodeConstants.MODEL_FIRST_NODE_NOT_USER_TASK);
                    }
                }
                break;
            }
        }
        return task;
    }
    
    /** 
     * @description  判断任务是否是多实例 - 会签类型
     * @param bpmnModel BpmnModel
     * @param taskKey 任务节点ID
     * @return java.lang.Boolean
     * @author junyuan.liu
     * @date 2022/7/1 10:47
     */ 
    public static Boolean isTaskMulti(BpmnModel bpmnModel, String taskKey){
        FlowElement flowElement = bpmnModel.getFlowElement(taskKey);
        //必须是任务类型
        if (flowElement instanceof Task) {
            Task task = (Task) flowElement;
            return task.hasMultiInstanceLoopCharacteristics();
        }
        return false;
    }
    
    /** 
     * @description 查找当前实例的 父实例 一直往上找
     * @param instance 当前实例
     * @param processList 所有的流程实例
     * @param dataList 数据  
     * @author junyuan.liu
     * @date 2022/8/15 13:33
     */ 
    public static void findParentProcess(HistoricProcessInstance instance, List<HistoricProcessInstance> processList, List<BpmProcessInstanceRespVO> dataList){
        if (CollectionUtil.isEmpty(processList)) {
            return;
        }
        dataList = dataList == null ? new ArrayList<>() : dataList;
        
        if (Objects.nonNull(instance) && StringUtils.isNotEmpty(instance.getSuperProcessInstanceId())) {
            HistoricProcessInstance i = CollectionUtils.findFirst(processList, p -> Objects.equals(p.getId(), instance.getSuperProcessInstanceId()));
            if (Objects.nonNull(i)) {
                dataList.add( BpmProcessInstanceConvert.INSTANCE.convert(i) );
                if (StringUtils.isNotEmpty(i.getSuperProcessInstanceId())) {
                    findParentProcess(i, processList, dataList);
                }
            }
        }
    }
    
    
}
